(ns tejst.unit
  (:import (tejst.unit TejstAssertionException)))

(defstruct test-result :test-name :test-ns :result :exception)

(defn find-tests [namespace]
  (map 
    apply 
    (filter 
      #(:is-test? (meta %1))
      (vals (ns-publics 
              (if (symbol? namespace) 
                (find-ns namespace) 
                namespace))))))


(defn- is-pass? [test-result]
  (= :pass (:result test-result)))

(defn- is-failure? [test-result]
  (= :fail (:result test-result)))

(defn- is-error? [test-result]
  (= :error (:result test-result)))

(defn- display-report [results passes failures errors]
  (println 
    (str 
      "Tests [" (count results) 
      "] Passes [" (count passes) 
      "] Failures [" (count failures) 
      "] Errors [" (count errors) "]"))
  
  (doseq [failure failures]
    (println 
      (str 
        "Test " (ns-name (:test-ns failure)) 
        "/" (:test-name failure) 
        " failed :"))
    (println " " (. (:exception failure) getMessage)))
  
  (doseq [error errors]
    (println 
      (str 
        "Test " (ns-name (:test-ns error)) 
        "/" (:test-name error) 
        " threw an exception :"))
    (. (:exception error) printStackTrace)))

(defn- execute-tests [tests]      
  (let [results (map #(%1) tests)
        passes (filter is-pass? results)
        failures (filter is-failure? results)
        errors (filter is-error? results)]
    
    (display-report results passes failures errors)))

(defn execute-tests-in [& namespaces]
  (execute-tests (mapcat find-tests namespaces)))

(defn execute-all-tests []
  (apply execute-tests-in (all-ns)))

(defmacro deftest [test-name & body]
  `(defn ~(with-meta test-name {:is-test? true}) [] 
     #(try 
        ~@body        
        (struct test-result ~(name test-name) ~*ns* :pass)
        
        (catch TejstAssertionException e# 
          (struct test-result ~(name test-name) ~*ns* :fail e#))
        (catch Exception e#
          (struct test-result ~(name test-name) ~*ns* :error e#)))))

(defmacro assert-equal [expected actual]
  `(when-not (= ~expected ~actual)
     (throw (new TejstAssertionException (str "Expected : " ~expected " but was : " ~actual)))))

(defmacro assert-not-nil [value]
  `(when (nil? ~value)
     (throw (new TejstAssertionException "Unexpected nil"))))		

(defmacro fail [message]
  `(throw (new TejstAssertionException ~message)))